package com.tju.shortlink.admin.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.tju.shortlink.admin.common.biz.user.UserContext;
import com.tju.shortlink.admin.common.convention.exception.ClientException;
import com.tju.shortlink.admin.common.convention.exception.ServiceException;
import com.tju.shortlink.admin.common.convention.result.Result;
import com.tju.shortlink.admin.dao.entity.GroupDo;
import com.tju.shortlink.admin.dao.entity.GroupUniqueDo;
import com.tju.shortlink.admin.dao.mapper.GroupMapper;
import com.tju.shortlink.admin.dao.mapper.GroupUniqueMapper;
import com.tju.shortlink.admin.dto.req.ShortLinkGroupSortReqDTO;
import com.tju.shortlink.admin.dto.req.ShortLinkGroupUpdateReqDTO;
import com.tju.shortlink.admin.dto.resp.ShortLinkGroupRespDTO;
import com.tju.shortlink.admin.remote.ShortLinkActualRemoteService;
import com.tju.shortlink.admin.remote.dto.req.ShortLinkRemoveByGidDTO;
import com.tju.shortlink.admin.remote.dto.resp.ShortLinkGroupCountQueryRespDTO;
import com.tju.shortlink.admin.service.IGroupService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tju.shortlink.admin.toolkit.RandomGenerator;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static com.tju.shortlink.admin.common.constant.RedisCacheConstant.LOCK_GROUP_CREATE_KEY;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author mxl
 * @since 2024-07-05
 */
@Service
@RequiredArgsConstructor
public class GroupServiceImpl extends ServiceImpl<GroupMapper, GroupDo> implements IGroupService {

    private final RedissonClient redissonClient;

    private final ShortLinkActualRemoteService shortLinkActualRemoteService;

    private final RBloomFilter<String> gidRegisterCachePenetrationBloomFilter;

    private final GroupUniqueMapper groupUniqueMapper;

    /**
     * 限制最大创建组数
     */
    @Value("${short-link.group.max-num}")
    private Integer groupMaxNum;

    @Override
    public void saveGroup(String groupName) {
        saveGroup(UserContext.getUsername(), groupName);
    }

    public void saveGroup(String username, String groupName) {
        // 1. 参数校验
        if(StrUtil.isBlank(groupName)){
            throw new ClientException("分组名不能为空");
        }
        // 2. 获取分布式所、防止并发创建超量的分组
        RLock lock = redissonClient.getLock(StrUtil.format(LOCK_GROUP_CREATE_KEY, username));
        lock.lock();
        try{
            LambdaQueryWrapper<GroupDo> queryWrapper = Wrappers.lambdaQuery(GroupDo.class)
                    .eq(GroupDo::getUsername, username)
                    .eq(GroupDo::getDelFlag, 0);
            // 2.1 获取当前分组数
            List<GroupDo> groupDoList = baseMapper.selectList(queryWrapper);
            if(CollUtil.isNotEmpty(groupDoList) && groupDoList.size() >= groupMaxNum){
                throw new ClientException(StrUtil.format("分组已超出最大分组数 {}", groupMaxNum));
            }
            // 2.2 生成对应的gid，并保存
            int retryCount = 0;
            int maxRetries = 10;
            String gid = null;
            while(retryCount < maxRetries){
                gid = saveGroupUniqueReturnGid();
                if(StrUtil.isNotBlank(gid)){
                    GroupDo groupDo = GroupDo.builder()
                            .gid(gid)
                            .sortOrder(0)
                            .name(groupName)
                            .username(username)
                            .build();
                    baseMapper.insert(groupDo);
                    gidRegisterCachePenetrationBloomFilter.add(gid);
                    break;
                }
                ++retryCount;
            }
            if(StrUtil.isEmpty(gid)){
                throw new ServiceException("生成分组标识频繁");
            }
        }finally {
            lock.unlock();
        }
    }

    private String saveGroupUniqueReturnGid() {
        String gid = RandomGenerator.generateRandom();
        if (gidRegisterCachePenetrationBloomFilter.contains(gid)) {
            return null;
        }
        GroupUniqueDo groupUniqueDO = GroupUniqueDo.builder()
                .gid(gid)
                .build();
        try {
            // 线程 A 和 B 同时生成了相同的 Gid，会被数据库的唯一索引校验触发异常
            // 流程不能被这个异常阻断，需要获取异常重试
            groupUniqueMapper.insert(groupUniqueDO);
        } catch (DuplicateKeyException e) {
            return null;
        }
        return gid;
    }

    @Override
    public List<ShortLinkGroupRespDTO> listGroup() {
        // 1. 获取当前用户
        String username = UserContext.getUsername();
        // 2. 查询数据库
        List<GroupDo> groupDoList = lambdaQuery()
                .eq(GroupDo::getUsername, username)
                .eq(GroupDo::getDelFlag, 0)
                .orderByDesc(GroupDo::getSortOrder, GroupDo::getUpdateTime)
                .list();
        // 3. 远程调用获取当前分组下的短链接数量
        Result<List<ShortLinkGroupCountQueryRespDTO>> listResult = shortLinkActualRemoteService
                        .listGroupShortLinkCount(groupDoList.stream().map(GroupDo::getGid).toList());

        // 4. 封装结果
        List<ShortLinkGroupRespDTO> shortLinkGroupRespDTOList = BeanUtil.copyToList(groupDoList, ShortLinkGroupRespDTO.class);
        shortLinkGroupRespDTOList.forEach(each -> {
            Optional<ShortLinkGroupCountQueryRespDTO> first = listResult.getData().stream()
                    .filter(item -> Objects.equals(item.getGid(), each.getGid()))
                    .findFirst();
            first.ifPresent(item -> each.setShortLinkCount(first.get().getShortLinkCount()));
        });
        return shortLinkGroupRespDTOList;
    }

    @Override
    public void updateGroup(ShortLinkGroupUpdateReqDTO requestParam) {
        // 1. 校验参数
        if(StrUtil.isBlank(requestParam.getName())){
            throw new ClientException("短链接分组更新失败");
        }
        // 2. 更新db
        boolean update = update(Wrappers.<GroupDo>lambdaUpdate()
                .eq(GroupDo::getUsername, UserContext.getUsername())
                .eq(GroupDo::getGid, requestParam.getGid())
                .eq(GroupDo::getDelFlag, 0)
                .set(GroupDo::getName, requestParam.getName())
        );
        if(!update){
            throw new ClientException("短链接分组更新失败");
        }
    }

    @Override
    @GlobalTransactional
    public void deleteGroup(String gid) {
        // 1. 操作db
        boolean update = update(Wrappers.<GroupDo>lambdaUpdate()
                .eq(GroupDo::getUsername, UserContext.getUsername())
                .eq(GroupDo::getGid, gid)
                .eq(GroupDo::getDelFlag, 0)
                .set(GroupDo::getDelFlag, 1)
        );
        if(!update){
            throw new ClientException("短链接分组删除失败");
        }
        ShortLinkRemoveByGidDTO shortLinkRemoveByGidDTO = new ShortLinkRemoveByGidDTO();
        shortLinkRemoveByGidDTO.setGid(gid);
        shortLinkActualRemoteService.removeLinkByGid(shortLinkRemoveByGidDTO);
    }

    @Override
    public void sortGroup(List<ShortLinkGroupSortReqDTO> requestParam) {
        requestParam.forEach(each -> {
            // 1. 封装排序信息
            GroupDo groupDO = GroupDo.builder()
                    .sortOrder(each.getSortOrder())
                    .build();
            // 2. 更新db
            LambdaUpdateWrapper<GroupDo> updateWrapper = Wrappers.lambdaUpdate(GroupDo.class)
                    .eq(GroupDo::getUsername, UserContext.getUsername())
                    .eq(GroupDo::getGid, each.getGid())
                    .eq(GroupDo::getDelFlag, 0);
            baseMapper.update(groupDO, updateWrapper);
        });
    }
}
